﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Model;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Ftp
{
    internal class FtpTransferClient : IDisposable
    {
        private bool _disposed = false;
        private readonly FtpSession _session;
        private readonly FileStream _stream = null;
        private readonly StreamWriter _streamWriter = null;
        private readonly Socket _client;
        private ThreadEx _receiveDataThread;
        private readonly AutoResetEvent _eventHandler = null;


        public Exception ReceiveException { get; set; }

        public FtpTransferClient(Socket client, FtpSession session, bool append, string filePath)
        {
            this._client = client;
            this._session = session;

            if (filePath != null)
            {
                if (append)
                {
                    this._stream = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
                }
                else
                {
                    this._stream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.Read);
                }

                switch (session.DataTransferFormat)
                {
                    case FtpDataTransferFormats.Image:
                        this._streamWriter = new StreamWriter(this._stream);
                        break;
                    case FtpDataTransferFormats.ASCII:
                        this._streamWriter = new StreamWriter(this._stream, Encoding.ASCII);
                        break;
                    case FtpDataTransferFormats.EBCDIC:
                        this._streamWriter = new StreamWriter(this._stream, session.FtpServer.Config.EBCDICEncoding);
                        break;
                    default:
                        ZLog.ZLoger.Warn($"ftp服务端未实现的传输格式类型\"{session.DataTransferFormat.ToString()}\"");
                        break;
                }

                this._eventHandler = new AutoResetEvent(false);
                this._receiveDataThread = new ThreadEx(this.ReceiveDataThreadMethod, $"ftp文件数据传输线程{Path.GetFileName(filePath)}");
                this._receiveDataThread.Start();
            }
        }


        private void ReceiveDataThreadMethod(ThreadPara threadPara)
        {
            try
            {
                byte[] buffer = new byte[NetHelper.TCP_PACKAGE];
                int revLength;
                while (!threadPara.Token.IsCancellationRequested)
                {
                    try
                    {
                        revLength = this._client.Receive(buffer);
                        if (revLength <= 0)
                        {
                            break;
                        }

                        this._stream.Write(buffer, 0, revLength);
                        this._stream.Flush();
                    }
                    catch (SocketException)
                    {
                        break;
                    }
                    catch (IOException ioex)
                    {
                        this.ReceiveException = ioex;
                        break;
                    }
                    catch (Exception exi)
                    {
                        ZLoger.Error(exi);
                        break;
                    }
                }

                this._eventHandler.Set();
            }
            catch (Exception ex)
            {
                ZLoger.Error(ex);
            }
        }


        public void Send(byte[] buffer)
        {
            this._client.Send(buffer);
        }

        public void SendFile(DataSource bufferData, FtpDataTransferFormats format)
        {
            using (var stream = new FileStream(bufferData.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                bufferData.UpdateFileBufferLength(stream.Length);

                switch (format)
                {
                    case FtpDataTransferFormats.Image:
                        this.SendStreamData(stream, bufferData.LongOffset, bufferData.LongLength);
                        break;
                    case FtpDataTransferFormats.ASCII:
                        this.SendStreamData(stream, bufferData.LongOffset, bufferData.LongLength, Encoding.ASCII);
                        break;
                    case FtpDataTransferFormats.EBCDIC:
                        this.SendStreamData(stream, bufferData.LongOffset, bufferData.LongLength, this._session.FtpServer.Config.EBCDICEncoding);
                        break;
                    default:
                        ZLoger.Warn($"ftp服务端未实现的传输格式类型\"{this._session.DataTransferFormat.ToString()}\"");
                        break;
                }
            }
        }

        private void SendStreamData(Stream stream, long offset, long length)
        {
            stream.Position = offset;
            long endPostion = stream.Position + length;
            int bufferSize;
            if (length <= NetHelper.TCP_PACKAGE)
            {
                bufferSize = (int)length;
            }
            else
            {
                bufferSize = NetHelper.TCP_PACKAGE;
            }

            byte[] buffer = new byte[NetHelper.TCP_PACKAGE];
            int sendLength;
            long modCharCount;

            while (stream.Position < endPostion)
            {
                modCharCount = (endPostion - stream.Position);
                if (modCharCount < bufferSize)
                {
                    bufferSize = (int)modCharCount;
                }

                sendLength = stream.Read(buffer, 0, bufferSize);
                this._client.Send(buffer, 0, sendLength, System.Net.Sockets.SocketFlags.None);
            }
        }


        private void SendStreamData(Stream stream, long offset, long length, Encoding encoding)
        {
            stream.Position = offset;
            long endPostion = stream.Position + length;
            int byteSize = sizeof(byte);
            int charSize = sizeof(char);
            int mult = charSize / byteSize;
            int bufferSize;
            if (length <= NetHelper.TCP_PACKAGE)
            {
                bufferSize = (int)length;
            }
            else
            {
                bufferSize = NetHelper.TCP_PACKAGE / mult;
            }
            char[] charBuffer = new char[bufferSize];
            byte[] buffer = new byte[NetHelper.TCP_PACKAGE];
            int sendLength;
            long mod, modCharCount;
            StreamReader streamReader = new StreamReader(stream, encoding);

            while (stream.Position < endPostion)
            {
                mod = endPostion - stream.Position;
                if (mod > mult)
                {
                    modCharCount = mod / mult;
                    if (modCharCount < bufferSize)
                    {
                        bufferSize = (int)modCharCount;
                    }

                    streamReader.Read(charBuffer, 0, bufferSize);
                    sendLength = encoding.GetBytes(charBuffer, 0, bufferSize, buffer, 0);
                }
                else
                {
                    buffer = encoding.GetBytes(streamReader.ReadToEnd());
                    sendLength = buffer.Length;
                }
                this._client.Send(buffer, 0, sendLength, System.Net.Sockets.SocketFlags.None);
            }
        }






        public void Set()
        {
            if (this._eventHandler != null)
            {
                try
                {
                    this._stream.Close();
                    this._eventHandler.Set();
                }
                catch (ObjectDisposedException)
                {

                }
            }
        }


        public void ReceiveData()
        {
            try
            {
                this._eventHandler.WaitOne();
            }
            catch (ObjectDisposedException)
            {

            }
        }

        public void Dispose()
        {
            if (this._disposed)
            {
                return;
            }
            this._disposed = true;

            if (this._receiveDataThread != null)
            {
                this._receiveDataThread.Dispose();
            }

            if (this._eventHandler != null)
            {
                this._eventHandler.Dispose();
                this._stream.Close();
            }

            this._client.Dispose();
        }
    }
}
